package com.soft.face.service.impl;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.soft.face.config.arcsoft.entity.UserCompareInfo;
import com.soft.face.config.arcsoft.service.FaceEngineService;
import com.soft.face.config.arcsoft.util.UserRamCache;
import com.soft.face.config.system.ServerConfig;
import com.soft.face.entity.UserFace;
import com.soft.face.mapper.UserFaceMapper;
import com.soft.face.service.UserFaceService;
import com.soft.face.utils.file.FileUploadUtils;
import com.soft.face.utils.file.FileUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

@Slf4j
@Service
@AllArgsConstructor
public class UserFaceServiceImpl extends ServiceImpl<UserFaceMapper, UserFace> implements UserFaceService {

    private Environment environment;
    private FaceEngineService faceEngineService;
    private ServerConfig serverConfig;

    /**
     * IO密集型建议：2*CPU，因为IO密集型线程不是一直在运行，所以可以配置多一点；
     * CPU密集型建议：因为一直在使用CPU，所以要保证线程数不能太多，可以CPU数+1；
     */
    private final static ExecutorService farExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private final static ExecutorService frrExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    @Override
    @Transactional
    public void upload(ZipFile zipFile) throws Exception {
        Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
        List<UserFace> list = new ArrayList<>();
        while (enumeration.hasMoreElements()) {
            ZipEntry zipEntry = enumeration.nextElement();
            if (!zipEntry.isDirectory()) {
                // 文件进行存储
                String name = zipEntry.getName();
                String[] split = name.split("/");
                String faceClass = name.substring(name.length() - (split[split.length - 2].length()
                        + split[split.length - 1].length()) - 1, name.length() - split[split.length - 1].length() - 1);
                InputStream inputStream = zipFile.getInputStream(zipEntry);
                MultipartFile multipartFile = FileUtils.getMultipartFile(inputStream, name);
                String url = serverConfig.getUrl() +FileUploadUtils.upload(multipartFile);
                // 人脸识别
                ImageInfo imageInfo = ImageFactory.getRGBData(URLUtil.getStream(new URL(url)));
                List<FaceInfo> faceInfos = faceEngineService.detectFaces(imageInfo);
                byte[] bytes = faceEngineService.extractFaceFeature(imageInfo, faceInfos.get(0));
                List<UserCompareInfo> userCompareInfos = faceEngineService.faceRecognition(bytes, UserRamCache.getUserList(), environment.getProperty("arcface.rate", Long.class));
                UserFace userFace = new UserFace();
                userFace.setFaceId(faceInfos.get(0).getFaceId() + "");
                if (!CollectionUtils.isEmpty(userCompareInfos)) {
                    userFace.setSimilar(userCompareInfos.get(0).getSimilar());
                }
                userFace.setFileUrl(url);
                userFace.setFaceClass(faceClass);
                userFace.setFaceFeature(bytes);
                list.add(userFace);
            }
        }
        SpringUtil.getBean(UserFaceService.class).saveBatch(list);
    }

    @Override
    public void cache() {
        // 讲人脸数据从数据库获取出来放入本地内存
        UserRamCache.removeAll();
        UserFaceService userFaceService = SpringUtil.getBean(UserFaceService.class);
        List<UserFace> userFaceList = userFaceService.list();
        if (!CollectionUtils.isEmpty(userFaceList)) {
            for (UserFace userFace : userFaceList) {
                UserRamCache.UserInfo userInfo = new UserCompareInfo();
                userInfo.setFaceId(userFace.getFaceId());
                userInfo.setFaceFeature(userFace.getFaceFeature());
                UserRamCache.addUser(userInfo);
            }
        }
        log.info("人脸数据缓存成功");
    }

    @Override
    public JSONObject analyse(List<Float> thresholds) throws Exception {
        // 百分比转换
        // NumberFormat percent = NumberFormat.getPercentInstance();
        // percent.setMaximumFractionDigits(2); // 精确小数点后两位
        Map<String, List<UserFace>> userFaceMap = SpringUtil.getBean(UserFaceService.class)
                .list().stream().collect(Collectors.groupingBy(UserFace::getFaceClass));
        List<List<String>> lists = userFaceMap.values().stream().map(item ->
                item.stream().map(UserFace::getFileUrl).collect(Collectors.toList())).collect(Collectors.toList());
        log.info("------------------FAR开始执行------------------");
        CompletableFuture<Map<Float, BigDecimal>> farFuture = CompletableFuture.supplyAsync(() -> {
            Map<Float, BigDecimal> farMap = new HashMap<>();
            // FAR计算 本应该匹配失败的判为匹配成功次数/类间匹配总次数(总的匹配失败次数)
            long startDate = System.currentTimeMillis();
            try {
                // 对比数据集
                List<Pair<List<String>, List<String>>> pairs = combineTwoElement(lists);
                List<List<String>> correlation = new ArrayList<>();
                for (Pair<List<String>, List<String>> pair : pairs) {
                    correlation.addAll(Lists.cartesianProduct(pair.getKey(), pair.getValue()));
                }
                // 分成10一组，多线程处理
                List<List<List<String>>> partition = Lists.partition(correlation, 50);
                ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(farExecutor);
                List<ListenableFuture<Map<Float, Integer>>> listenableFutureList = new ArrayList<>();
                for (List<List<String>> list : partition) {
                    ListenableFuture<Map<Float, Integer>> future = listeningExecutorService.submit(new FAR(list, thresholds));
                    listenableFutureList.add(future);
                }
                ListenableFuture<List<Map<Float, Integer>>> listListenableFuture = Futures.allAsList(listenableFutureList);
                // 识别错误
                List<Map<Float, Integer>> maps = listListenableFuture.get();
                Map<Float, Integer> result = new HashMap<>();
                for (Map<Float, Integer> map : maps) {
                    for (Float k : map.keySet()) {
                        Integer v = result.get(k);
                        if (v == null) {
                            result.put(k, map.get(k));
                        } else {
                            result.put(k, v + map.get(k));
                        }
                    }
                }
                for (Float k : result.keySet()) {
                    int size = userFaceMap.values().stream().findFirst().get().size();
                    // 匹配的总次数
                    int total = (userFaceMap.keySet().size() * size * (userFaceMap.keySet().size() * size - 1));
                    // 总的匹配失败次数
                    int fail = total - (userFaceMap.keySet().size() * size * (size - 1));
                    BigDecimal far = new BigDecimal(result.get(k)).divide(new BigDecimal(fail), 2, BigDecimal.ROUND_HALF_UP);
                    farMap.put(k, far);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long endDate = System.currentTimeMillis();
            log.info(("FAR耗时时间: " + (endDate - startDate) + " ms"));
            log.info("------------------FAR结束执行------------------");
            return farMap;
        });

        CompletableFuture<Map<Float, BigDecimal>> frrFuture = CompletableFuture.supplyAsync(() -> {
            Map<Float, BigDecimal> frrMap = new HashMap<>();
            long startDate = System.currentTimeMillis();
            // FRR计算
            try {
                log.info("------------------FRR开始执行------------------");
                // FRR计算 本应该匹配成功的判为匹配失败次数/类内匹配总次数(总的匹配成功次数)
                // 对比数据集
                List<Pair<String, String>> correlation = new ArrayList<>();
                for (List<String> list : lists) {
                    List<Pair<String, String>> pairs = combineTwoElement(list);
                    if (!CollectionUtils.isEmpty(pairs)) {
                        correlation.addAll(pairs);
                    }
                }

                // 分成10一组，多线程处理
                List<List<Pair<String, String>>> partition = Lists.partition(correlation, 50);
                ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(frrExecutor);
                List<ListenableFuture<Map<Float, Integer>>> listenableFutureList = new ArrayList<>();
                for (List<Pair<String, String>> list : partition) {
                    ListenableFuture<Map<Float, Integer>> future = listeningExecutorService.submit(new FRR(list, thresholds));
                    listenableFutureList.add(future);
                }
                ListenableFuture<List<Map<Float, Integer>>> listListenableFuture = Futures.allAsList(listenableFutureList);
                // 识别错误
                List<Map<Float, Integer>> maps = listListenableFuture.get();
                Map<Float, Integer> result = new HashMap<>();
                for (Map<Float, Integer> map : maps) {
                    for (Float k : map.keySet()) {
                        Integer v = result.get(k);
                        if (v == null) {
                            result.put(k, map.get(k));
                        } else {
                            result.put(k, v + map.get(k));
                        }
                    }
                }
                for (Float k : result.keySet()) {
                    int size = userFaceMap.values().stream().findFirst().get().size();
                    // 匹配成功次数
                    int success = userFaceMap.keySet().size() * size * (size - 1);
                    BigDecimal frr = new BigDecimal(result.get(k)).divide(new BigDecimal(success), 2, BigDecimal.ROUND_HALF_UP);
                    frrMap.put(k, frr);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long endDate = System.currentTimeMillis();
            log.info(("FRR耗时时间: " + (endDate - startDate) + " ms"));
            log.info("------------------FRR结束执行------------------");
            return frrMap;
        });


        CompletableFuture<Void> completableFuture = CompletableFuture.allOf(farFuture, frrFuture);
        completableFuture.join();

        // 对数据做排序和百分比转换处理
        JSONObject jsonObject = new JSONObject(true);
        Map<Float, BigDecimal> farMap = new LinkedHashMap<>();
        farFuture.get().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(item -> farMap.put(item.getKey(), item.getValue()));
        Map<Float, BigDecimal> frrMap = new LinkedHashMap<>();
        frrFuture.get().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(item -> frrMap.put(item.getKey(), item.getValue()));
        jsonObject.append("far", new LinkedList<>(farMap.values()));
        jsonObject.append("frr", new LinkedList<>(frrMap.values()));
        return jsonObject;
    }

    /**
     * FAR计算
     */
    public class FAR implements Callable<Map<Float, Integer>> {

        private List<List<String>> list;
        // 阈值列表
        private List<Float> thresholds;

        public FAR(List<List<String>> list, List<Float> thresholds) {
            this.list = list;
            this.thresholds = thresholds;
        }

        @Override
        public Map<Float, Integer> call() throws Exception {
            Map<Float, Integer> map = new HashMap<>();
            for (List<String> urls : list) {
                ImageInfo imageInfo1 = ImageFactory.getRGBData(URLUtil.getStream(new URL(urls.get(0))));
                ImageInfo imageInfo2 = ImageFactory.getRGBData(URLUtil.getStream(new URL(urls.get(1))));
                Float face = SpringUtil.getBean(FaceEngineService.class).compareFace(imageInfo1, imageInfo2);
                for (Float threshold : thresholds) {
                    if (face >= threshold) {
                        map.merge(threshold, 1, Integer::sum);
                    } else {
                        map.put(threshold, 0);
                    }
                }
            }
            return map;
        }
    }

    /**
     * FRR计算
     */
    public class FRR implements Callable<Map<Float, Integer>> {

        private List<Pair<String, String>> list;
        // 阈值列表
        private List<Float> thresholds;

        public FRR(List<Pair<String, String>> list, List<Float> thresholds) {
            this.list = list;
            this.thresholds = thresholds;
        }

        @Override
        public Map<Float, Integer> call() throws Exception {
            Map<Float, Integer> map = new HashMap<>();
            for (Pair<String, String> pair : list) {
                ImageInfo imageInfo1 = ImageFactory.getRGBData(URLUtil.getStream(new URL(pair.getKey())));
                ImageInfo imageInfo2 = ImageFactory.getRGBData(URLUtil.getStream(new URL(pair.getValue())));
                Float face = SpringUtil.getBean(FaceEngineService.class).compareFace(imageInfo1, imageInfo2);
                for (Float threshold : thresholds) {
                    if (face < threshold) {
                        map.merge(threshold, 1, Integer::sum);
                    } else {
                        map.put(threshold, 0);
                    }
                }
            }
            return map;
        }
    }

    public static <T> List<Pair<T, T>> combineTwoElement(List<T> list) {
        List<Pair<T, T>> resultList = Lists.newArrayListWithExpectedSize(list.size() * list.size() / 2);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        if (list.size() >= 2) {
            for (int j = 0; j < list.size(); j++) {
                resultList.addAll(ll(list.subList(j, list.size())));
            }

        }
        return resultList;
    }


    public static <T> List<Pair<T, T>> ll(List<T> list) {
        List<Pair<T, T>> resultList = Lists.newArrayListWithExpectedSize(list.size() * list.size() / 2);

        for (int j = 1; j < list.size(); j++) {
            resultList.add(new Pair(list.get(0), list.get(j)));
        }
        return resultList;
    }


}
